/** \brief File implementing a class managing a s of Cell instances.
 *
 * This file contains the implementation of a matrix of Cell instances.
 *
 * @file GridBase.cpp
 * @author Johan "Solo" Luisier
 * @date 2013/03/22
 */
#include "GridBase.hpp"

#include "Cell.hpp"
#include "RuleData.hpp"
#include "Worker.hpp"

#include <QtCore/QtConcurrentMap>
#include <QtCore/QFutureWatcher>
#include <QtCore/QDebug>

using namespace Worker;

GridBase::GridBase( const quint32& w, const quint32& h, QObject* parent )
    : QObject( parent )
    , Running( false )
    , Width( w )
    , Height( h )
    , CellNumber( 0u )
    , Matrix()
    , GameRules( 0 )
    , CellMapping()
{
}

GridBase::GridBase( QObject* parent )
    : QObject( parent )
    , Running( false )
    , Width( 0u )
    , Height( 0u )
    , CellNumber( 0u )
    , Matrix()
    , GameRules( 0 )
    , CellMapping()
{
}

GridBase::~GridBase()
{
    foreach ( Cell* cell, Matrix )
    {
        delete cell;
        cell = 0;
    }

    Matrix.clear();
}

const bool& GridBase::isRunning() const
{
    return Running;
}

const quint32& GridBase::width() const
{
    return Width;
}

void GridBase::setWidth( const quint32& w )
{
    if ( isRunning() )
        qCritical( qPrintable( tr( "GridBase::Width cannot be set while running" ) ) );
    else
        Width = w;
}

const quint32& GridBase::height() const
{
    return Height;
}

void GridBase::setHeight( const quint32& h )
{
    if ( isRunning() )
        qCritical( qPrintable( tr( "GridBase::Height cannot be set while running" ) ) );
    else
        Height = h;
}

const quint32& GridBase::cellNumber() const
{
    return CellNumber;
}

const Cell* GridBase::at( const quint32& i, const quint32& j ) const
{
    if ( i >= Height || j >= Width )
        return 0;
    else
        return Matrix[ i * Width + j ];
}

Cell* GridBase::operator()( const quint32& i, const quint32& j )
{
    if ( i >= Height || j >= Width )
        return 0;
    else
        return Matrix[ i * Width + j ];
}

void GridBase::step()
{
    if ( ! isRunning() )
    {
        qFatal( qPrintable( tr( "Try to run an uninitialised instance." ) ) );
        return;
    }

    QFutureWatcher< void > *watcher = new QFutureWatcher< void >;

    watcher -> setFuture( QtConcurrent::map( Matrix, &getFutureStatus ) );

    watcher -> waitForFinished();

    watcher -> setFuture( QtConcurrent::map( Matrix, &evolveCell ) );

    watcher -> waitForFinished();

    delete watcher;
    watcher = 0;

    emit( stepComputed() );
}

bool GridBase::initialiseMatrix()
{
    qDebug() << tr( "GridBase::initialiseMatrix basic initialisation starting" );

    if ( width() == 0u || height() == 0u )
    {
        qFatal( "GridBase::Matrix dimensions not initialised" );
        return false;
    }

    for ( quint32 i( 0u ), j; i < Height; i++ )
    {
        for ( j = 0u; j < Width; j++ )
        {
            Matrix.append( new Cell( this ) );
            CellMapping.insert( Matrix.last(), QPoint( i, j ) );
            connect( Matrix.last(), SIGNAL( flipped( Cell* ) ),
                     this, SLOT( cellFlipped( Cell* ) ) );
        }
    }

    qDebug() << tr( "GridBase::initialiseMatrix basic initialisation done" );

    return initialiseNeighbours();
}

void GridBase::initialise()
{
    Running = initialiseMatrix();
}

void GridBase::cellFlipped( Cell* cell )
{
    QHash< Cell*, QPoint >::const_iterator it, End( CellMapping.end() );

    qDebug() << "Cell flip detected";

    it = CellMapping.find( cell );

    if ( it == End )
        emit( flipped( QPoint( -1, -1 ) ) );
    else
        emit( flipped( it.value() ) );
}

void GridBase::flipCell( const QPoint& pt )
{
    if ( static_cast< quint32 >( pt.x() ) >= height() &&
         static_cast< quint32 >( pt.y() ) >= width() )
        return;

    if ( at( pt.x(), pt.y() ) -> neighbours() . size() == 0 )
        return;

    operator()( pt.x(), pt.y() ) -> toggleCurrentStatus();
}
